In many cases, the "fire-and-forget" nature of
asynchronous communication may be applied only to prevent client
blocking, not because there is no desire to find out the result of the
service call. The options for retrieving service results depend heavily
on the types of service clients and expected availability of both ends
of the service transactions.
Ideally, you'd want your
service client to be able to make outbound calls and also host inbound
ones. This sort of solution where both the client and service can send
messages to each other independently is called a duplex service.
In this fashion, our client could make an asynchronous call, and the
service could deliver an out-of-band response event that the client
would handle accordingly. However, this approach is neither
interoperable nor accommodating to clients and services that are online
at different times.
A service designer who
is most interested in supporting the widest range of consumers will
offer both polling and service callback options. For the polling
scenario, the service updates an agreed upon repository upon completion,
and it remains the client's responsibility to poll this repository to
discover the service output. This repository could be a database, file
system, queue, or even a URL in the case of the service building a
"resource" as a completion output and the service client issuing RESTful
calls for said resource. Polling also works well in cases where we
don't know if both the service and client are online at the same time.
By using an intermediary repository, the service could be unavailable,
but its resulting output is residing in a location accessible by the
client at any time.
The problem with polling is
that it's both unpredictable and expensive. By unpredictable, I mean
that the client cannot know whether the result will come back in one
second, three seconds, or thirty seconds, and therefore must have an
algorithm to poll at distinct times and hope for the best. It's
expensive because the client must inevitably waste processing cycles
polling for something that may or may not return in a timely fashion.
While not optimal for performance, this mechanism works well in
cross-platform scenarios where providing generic access to shared
repositories is straightforward.
Client callbacks are a
solid choice when both the client and service are online simultaneously
and both ends of the service transaction use WCF as their service
technology. WCF has hidden much of the complexity of this technique and
exposed a powerful way to alert users to results of asynchronous
processing.
Building WCF services that support client callbacks
WCF has rich support
for client callbacks and efficiently handles service response events.
What you need in order to support duplex patterns is a service contract
that requires sessions, and one of the two available duplex bindings in
WCF: NetTcpBinding and WSDualHttpBinding.
Duplex bindings are designed for scenarios where you want to open a
two-way communication between endpoints and potentially send numerous
messages both ways. However, this technique also fits the bill if you
simply want a single result returned from an asynchronous invocation.
Let's take a look at how to set up this exchange pattern to accomplish
the following scenario:
First of all, we
need new service interfaces to accommodate our duplex scenario. Our
first interface represents the service operation called by the client,
and the second interface represents the client operation invoked by the
service.
public interface IAdverseEventDuplex
{
[OperationContract(IsOneWay = true)]
void SubmitAdverseEvent(AdverseEvent NewAE);
}
public interface IAdverseEventDuplexCallback
{
[OperationContract(IsOneWay=true)]
void AEResult(AdverseEventAction aeAction);
}
Now that we have the
interfaces, we need to decorate the primary service interface with the
attributes necessary to support the WCF client callback capability.
Specifically, we need to apply both a session requirement and callback contract to the service. The callback contract references an interface that must be implemented by the service client.
[ServiceContract(
Namespace = "http://Seroter.BizTalkSOA.Chapter6",
SessionMode=SessionMode.Required,
CallbackContract=typeof(IAdverseEventDuplexCallback))]
public interface IAdverseEventDuplex
{
[OperationContract(IsOneWay = true)]
void SubmitAdverseEvent(AdverseEvent NewAE);
}
Our subsequent service implements the IAdverseEventDuplex interface. Notice that we extract the client's callback mechanism from the OperationContext object and then proceed to execute the client's AEResult function.
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class AdverseEventDuplexService : IAdverseEventDuplex
{
public void SubmitAdverseEvent(AdverseEvent NewAE)
{
System.Diagnostics.EventLog.WriteEntry("Duplex Service", "New AE received");
asynchronous communicationWCF services, building AdverseEventAction aeAction = new AdverseEventAction();
aeAction.PatientID = NewAE.PatientID;
aeAction.Product = NewAE.Product;
aeAction.doAdmitHospital = true;
//access callback object
IAdverseEventDuplexCallback callback = OperationContext.Current.GetCallbackChannel<IAdverseEventDuplexCallback>();
//sleep for two minutes
Thread.Sleep(120000);
callback.AEResult(aeAction);
}
}
Now that we have a service
implementation defined, we can add this to our existing WCF Service host
container project. To do this, add a new .svc
file and make sure the service directive points to this new service
class. Finally, we have to add a new service endpoint in the
configuration file associated with this service. I used the WsDualHttpBinding
so that we can see how to do callbacks over the HTTP protocol. Our
duplex service is now hosted by IIS 7.0 and available for browsing and
execution.
On the client side, we need to
create a class that implements the anticipated callback interface, and
pass that class as context to the duplex service. First, we add a
service reference to the duplex service hosted in IIS 7.0. Because this
is a duplex service with a callback contract, the client proxy
constructor now accepts an additional parameter:
public AdverseEventDuplexClient(
InstanceContext callbackInstance, string endpointConfigurationName) :
base(callbackInstance, endpointConfigurationName) {}
Notice that an InstanceContext
is now part of the constructor signature. This acts as the pointer back
to the client object that will be executed when the callback occurs. In
our client code, we need to first create a class which implements the
callback interface we defined earlier.
class AECallbackHandler : IAdverseEventDuplexCallback
{
public void AEResult(AdverseEventAction aeAction)
{
Console.WriteLine(
"AE result for patient {0} says it is {1} that they should be admitted to a hospital",
aeAction.PatientID,
aeAction.doAdmitHospital.ToString());
Console.ReadLine();
asynchronous communicationWCF services, building }
}
At this point we have all that
we need to call our service and receive an asynchronous response. The
code needed to execute our service operation looks like this:
private static void CallDuplexService()
{
Console.WriteLine("Calling duplex service ...");
//create instance context
InstanceContext context = new InstanceContext (new AECallbackHandler());
AdverseEventDuplexClient client = new AdverseEventDuplexClient (context, "AEDuplexEndpoint");
try
{
AdverseEvent newAE = new AdverseEvent();
newAE.PatientID = 100912;
newAE.PhysicianID = 7543;
newAE.Product = "Cerinob";
newAE.ReportedBy = ReportedByType.Patient;
newAE.Category = AECategoryType.InjectionSoreness;
newAE.DateStarted = new DateTime(2008, 10, 29);
client.SubmitAdverseEvent(newAE);
Console.WriteLine("Doing other things ...");
//Reader TODO; pick where to close this proxy AFTER callback is received
//client.Close();
Console.ReadLine();
}
catch (System.ServiceModel.CommunicationException) { client.Abort(); }
catch (System.TimeoutException) { client.Abort(); }
catch (System.Exception) { client.Abort(); throw; }
}
We created an InstanceContext object which references our new AECallbackHandler class and pass that object into the proxy constructor. Once we execute our operation (which if you recall has isOneWay set to True), we are free to do anything else we wish while waiting for the asynchronous response message. SubmitAdverseEvent
What is happening behind the scenes? When using the WsDualHttpBinding,
you actually end up with your service client briefly acting as a
service host as well. That is, after you call the primary operation, a
temporary endpoint is hosted by our client application. The HTTP address
of this endpoint is passed along with the initial request so that the
service knows where to send the response to. We can demonstrate this
technique in two ways. First, we can add an extended delay to our
service implementation which extends beyond the standard service timeout
window. This proves that we are not making a pseudo-asynchronous call,
which actually relies on a synchronous pattern. The second way to verify
this concept is to turn on WCF diagnostics (applied by configuring the Diagnostics
node of the client's configuration file via the WCF Service
Configuration Editor) and watch the traffic that moves between the
client and service. In fact, we can observe that our service request
message has its temporary callback address stored in the ReplyTo node sent to the service.
Callbacks in WCF are a very powerful way to transmit data between clients and services in both directions.